﻿using System.Collections.Generic;
using System.Text;
using FunctionalCLR.Core;
using Zove.Runtime;

namespace Zove.Query {
    public class Query {

        private string query;

        public List<string> Select {
            get;
            set;
        }

        public string From {
            get;
            set;
        }

        public Dictionary<string, string> Where {
            get;
            set;
        }

        public List<string> Assert {
            get;
            set;
        }

        public Query() {
            this.Select = new List<string>();
            this.Assert = new List<string>();
            this.Where = new Dictionary<string, string>();
        }

        public Query(string query)
            : this() {
            this.query = query;
        }

        public Instance Solution {
            get;
            set;
        }

        public RuntimeModel Model {
            get {
                return this.Solution.Model;
            }
        }

        public List<QueryResult> Run() {
            this.Solution = new Instance(From);

            DefaultRuntimeModelLoader drml
                = (DefaultRuntimeModelLoader)
                ZoveDependencyManager.Instance.RuntimeModelLoader;
            drml.Reset();

            ((DefaultClassLoader)
                ZoveDependencyManager.Instance.ClassLoader).ParserFactory
                = ZoveDependencyManager.Instance.ParserFactory;
            ((DefaultClassLoader)
                ZoveDependencyManager.Instance.ClassLoader).FunctionManager
                = ZoveDependencyManager.Instance.FunctionManager;
            this.Solution.Model = drml.LoadByClassName(From);
            //if (drml.Logger.GetErrorMessages().Count > 0) {
            //    throw new ModelLoadingException(drml.Logger.GetErrorMessages());
            //}

            foreach (string s in Where.Keys) {
                this.Solution.Assign(s, Where[s]);
            }

            this.Solution.Evaluate();

            int c = 0;
            string mathematicalExpression = string.Empty;
            QueryResult result;
            List<QueryResult> results = new List<QueryResult>();
            if (Select.Count == 1 && Select[0] == "*") {
                foreach (RuntimeFactor f in this.Solution.Model.Factors) {
                    if (f.Status == RuntimeFactorStatus.Solved) {
                        mathematicalExpression = f.Formulate();
                    }
                    result = new QueryResult(
                        f.Name,
                            f.Status == RuntimeFactorStatus.Unknown ?
                            "<N/A>" : f.Value.ToString(), f.Unit,
                            f.Status == RuntimeFactorStatus.Input
                            ? f.Name : mathematicalExpression);
                    results.Add(result);
                }
            } else {
                if (Select.Count > 0) {
                    #region select
                    foreach (string s in Select) {
                        RuntimeFactor rf = this.Solution.Model.GetFactorByName(s);
                        if (rf == null) {
                            string value
                                = (string)
                                ZoveDependencyManager.Instance.ParserFactory.Evaluate(
                                s, this.Solution.Model);

                            result = new QueryResult(s, value, "", "N/A");
                        } else {
                            string message = string.Empty;

                            if (rf.Status == RuntimeFactorStatus.Unknown) {
                                message = "<N/A>";
                            } else {
                                mathematicalExpression = rf.Formulate();

                                c++;
                                message = rf.Value.ToString();
                            }

                            result = new QueryResult(
                                s, message, rf.Unit, mathematicalExpression);
                        }
                        results.Add(result);
                    }
                    #endregion
                } else if (this.Assert.Count > 0) {
                    #region Assert
                    foreach (string s in this.Assert) {
                        string expr = s.Split('=')[0];
                        string expected = s.Split('=')[1];

                        RuntimeFactor rf = this.Solution.Model.GetFactorByName(expr);
                        if (rf == null) {
                            string value
                                = (string)
                                ZoveDependencyManager.Instance.ParserFactory.Evaluate(
                                s, this.Solution.Model);

                            if (value != expected) { throw new AssertException(value, expected); }
                        } else {
                            string message = string.Empty;

                            if (rf.Status == RuntimeFactorStatus.Unknown) {
                                throw new AssertException("Factor {0} is not available", rf.Name);
                            } else {
                                mathematicalExpression = rf.Formulate();

                                c++;
                                message = rf.Value.ToString();
                                if (rf.Value.ToString() != expected) {
                                    throw new AssertException(rf.Value.ToString(), expected);
                                }
                            }

                            result = new QueryResult(
                                s, message, rf.Unit, mathematicalExpression);
                        }
                    }
                    #endregion
                }
            }

            return results;
        }

        public override string ToString() {
            StringBuilder b = new StringBuilder();

            if (Select.Count > 0) {
                b.Append("select ");
                foreach (string s in Select) {
                    b.AppendFormat("{0}, ", s);
                }
            } else if (Assert.Count > 0) {
                b.Append("assert ");
                foreach (string s in Assert) {
                    b.AppendFormat("{0}, ", s);
                }
            }

            b.Append("from " + From);

            b.Append("where ");
            foreach (string s in Where.Keys) {
                b.AppendFormat("{0}={1}, ", s, Where[s]);
            }

            return b.ToString();
        }
    }
}